package org.ghost.springboot.demo.util.http;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.ghost.springboot.demo.component.util.SpringUtil;
import org.ghost.springboot.demo.util.UriUtil;
import org.ghost.springboot.demo.util.http.annotation.HttpClientConfig;
import org.ghost.springboot.demo.util.http.annotation.HttpRequestConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

public final class HttpClientProxy<T> implements InvocationHandler {
    private static final Logger logger = LoggerFactory.getLogger(HttpClientProxy.class);
    private static final String PARAM_FORMAT_BEGIN = "${";
    private static final String PARAM_FORMAT_END = "}";

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Class clz = method.getDeclaringClass();
        if (clz != null) {
            HttpClientConfig clientConfig = (HttpClientConfig) (clz.getAnnotation(HttpClientConfig.class));
            HttpRequestConfig requestConfig = method.getAnnotation(HttpRequestConfig.class);
            if (clientConfig != null && requestConfig != null) {
                List<ParameterObj> parameterObjList = this.getParameters(method, args);
                HttpRequestBuilder httpRequestBuilder = this.constructHttpRequestBuilder(clientConfig, requestConfig, parameterObjList);
                if (httpRequestBuilder != null) {
                    if (method.getGenericReturnType() instanceof ParameterizedType) {
                        return HttpClientHelper.invoke(httpRequestBuilder, (ParameterizedType) method.getGenericReturnType());
                    } else if (void.class != method.getReturnType()) {
                        return HttpClientHelper.invoke(httpRequestBuilder, method.getReturnType());
                    } else {
                        HttpClientHelper.invoke(httpRequestBuilder);
                    }
                }
            }
        }

        return null;
    }

    private HttpRequestBuilder constructHttpRequestBuilder(HttpClientConfig clientConfig, HttpRequestConfig requestConfig, List<ParameterObj> parameterObjList) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        final Class<HttpRequestBuilder> configClz = clientConfig.config();
        Constructor<HttpRequestBuilder> constructor = configClz.getConstructor(HttpMethod.class, String.class);
        if (constructor != null) {
            final Environment environment = SpringUtil.getEnvironment();
            //处理url参数
            final String fullUrl = this.getFullUrl(clientConfig, requestConfig, environment, parameterObjList);
            HttpRequestBuilder httpRequestBuilder = constructor.newInstance(requestConfig.method(), fullUrl);

            //header处理
            if (StringUtils.isNotBlank(requestConfig.contentType())) {
                httpRequestBuilder.addHeader(HttpHeaders.CONTENT_TYPE, requestConfig.contentType());
            } else {
                httpRequestBuilder.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
            }
            if (ArrayUtils.isNotEmpty(requestConfig.headers())) {
                for (String item : requestConfig.headers()) {
                    if (StringUtils.isNotBlank(item)) {
                        String[] array = this.getFormatString(environment, item).split("=");
                        if (ArrayUtils.isNotEmpty(array) && array.length == 2) {
                            String key = array[0];
                            String value = array[1];
                            if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) {
                                httpRequestBuilder.addHeader(key, value);
                            }
                        }
                    }
                }
            }

            //RequestBody处理
            if (CollectionUtils.isNotEmpty(parameterObjList)) {
                parameterObjList.stream()
                        .filter(Objects::nonNull)
                        .filter(it -> ArrayUtils.isNotEmpty(it.getAnnotations()) && Stream.of(it.getAnnotations()).anyMatch(f -> f instanceof RequestBody))
                        .findFirst()
                        .ifPresent((val) -> httpRequestBuilder.addBody(val.getValue()));
            }

            return httpRequestBuilder;
        }

        return null;
    }

    private String getFullUrl(HttpClientConfig clientConfig, HttpRequestConfig requestConfig, Environment environment) {
        if (clientConfig != null && requestConfig != null) {
            String fullUrl = this.getFormatString(environment, clientConfig.baseUrl() + "/" + requestConfig.name());
            //处理注解中的参数
            if (ArrayUtils.isNotEmpty(requestConfig.params())) {
                String annoParaUrl = Stream.of(requestConfig.params())
                        .filter(StringUtils::isNotBlank)
                        .map(it -> getFormatString(environment, it))
                        .reduce((a, b) -> {
                            if (StringUtils.isNotBlank(a)) {
                                return a + "&" + b;
                            } else {
                                return b;
                            }
                        }).orElse(null);
                if (StringUtils.isNotBlank(annoParaUrl)) {
                    if (fullUrl.contains("?")) {
                        fullUrl = fullUrl + "&" + annoParaUrl;
                    } else {
                        fullUrl = fullUrl + "?" + annoParaUrl;
                    }
                }
            }

            return fullUrl;
        }

        return "";
    }

    private String getFullUrl(String url, List<ParameterObj> parameterObjList) {
        if (StringUtils.isNotBlank(url) && CollectionUtils.isNotEmpty(parameterObjList)) {
            //处理PathVariable
            for (ParameterObj parameterObj : parameterObjList) {
                if (parameterObj != null && ArrayUtils.isNotEmpty(parameterObj.getAnnotations())) {
                    String name = Stream.of(parameterObj.getAnnotations()).filter(Objects::nonNull).filter(it -> it instanceof PathVariable).map(it -> ((PathVariable) it).name()).findFirst().orElse(null);
                    if (StringUtils.isNotBlank(name)) {
                        url = url.replace("{" + name + "}", String.valueOf(parameterObj.getValue()));
                    }
                }
            }

            //处理RequestParam
            String paraUrl = parameterObjList.stream()
                    .filter(Objects::nonNull)
                    .filter(it -> ArrayUtils.isNotEmpty(it.getAnnotations()))
                    .map(it -> Stream.of(it.getAnnotations())
                            .filter(Objects::nonNull)
                            .filter(f -> f instanceof RequestParam)
                            .map(f -> {
                                String key = ((RequestParam) f).name();
                                if (it.getValue() == null || hasBaseType(it.getValue())) {
                                    if (StringUtils.isNotBlank(key)) {
                                        try {
                                            return key + "=" + URLEncoder.encode(String.valueOf(it.getValue()), "UTF-8");
                                        } catch (UnsupportedEncodingException e) {
                                            logger.warn("*****HttpClientProxy.getFullUrl编码失败, 错误信息: {}, {}", e.getMessage(), e);
                                        }
                                    }
                                    return "";
                                } else if (it.getValue() instanceof List) {
                                    if (StringUtils.isNotBlank(key)) {
                                        @SuppressWarnings("unchecked")
                                        List<Object> list = (List<Object>) (it.getValue());
                                        String value = list.stream().filter(Objects::nonNull).map(Object::toString).reduce("", (a, b) -> StringUtils.isBlank(a) ? b : a + "," + b);
                                        return key + "=" + value;
                                    }
                                    return "";
                                } else if (it.getValue().getClass().isArray()) {
                                    if (StringUtils.isNotBlank(key)) {
                                        Object[] array = (Object[]) (it.getValue());
                                        String value = Stream.of(array).filter(Objects::nonNull).map(Object::toString).reduce("", (a, b) -> StringUtils.isBlank(a) ? b : a + "," + b);
                                        return key + "=" + value;
                                    }
                                    return "";
                                } else {
                                    String value = UriUtil.urlParaEncode(it.getValue());
                                    if (StringUtils.isNotBlank(value) && !value.contains("=")) {
                                        return key + "=" + value;
                                    }
                                    return value;
                                }
                            })
                            .filter(StringUtils::isNotBlank)
                            .reduce((a, b) -> {
                                if (StringUtils.isNotBlank(a)) {
                                    return a + "&" + b;
                                } else {
                                    return b;
                                }
                            }).orElse(null))
                    .filter(StringUtils::isNotBlank)
                    .reduce((a, b) -> {
                        if (StringUtils.isNotBlank(a)) {
                            return a + "&" + b;
                        } else {
                            return b;
                        }
                    }).orElse(null);
            if (StringUtils.isNotBlank(paraUrl)) {
                if (url.contains("?")) {
                    url = url + "&" + paraUrl;
                } else {
                    url = url + "?" + paraUrl;
                }
            }

            //处理没有任何注解的参数
            String noAnnoUrl = parameterObjList.stream()
                    .filter(Objects::nonNull)
                    .filter(it -> ArrayUtils.isEmpty(it.getAnnotations()))
                    .filter(it -> it.getValue() != null)
                    .filter(it -> !hasBaseType(it.getValue()))
                    .map(UriUtil::urlParaEncode)
                    .filter(StringUtils::isNotBlank)
                    .reduce((a, b) -> {
                        if (StringUtils.isNotBlank(a)) {
                            return a + "&" + b;
                        } else {
                            return b;
                        }
                    }).orElse(null);
            if (StringUtils.isNotBlank(noAnnoUrl)) {
                if (url.contains("?")) {
                    url = url + "&" + noAnnoUrl;
                } else {
                    url = url + "?" + noAnnoUrl;
                }
            }
        }

        return url;
    }

    private String getFullUrl(HttpClientConfig clientConfig, HttpRequestConfig requestConfig, Environment environment, List<ParameterObj> parameterObjList) {
        return this.getFullUrl(this.getFullUrl(clientConfig, requestConfig, environment), parameterObjList);
    }

    private List<ParameterObj> getParameters(Method method, Object[] args) {
        if (ArrayUtils.isNotEmpty(args)) {
            Parameter[] parameters = method.getParameters();
            if (ArrayUtils.isNotEmpty(parameters) && ArrayUtils.isNotEmpty(args) && parameters.length == args.length) {
                List<ParameterObj> rtnList = new ArrayList<ParameterObj>();
                for (int i = 0; i < parameters.length; i++) {
                    rtnList.add(new ParameterObj(parameters[i].getName(), args[i], parameters[i].getAnnotations()));
                }

                return rtnList;
            }
        }

        return null;
    }

    private boolean hasBaseType(Object object) {
        if (object != null) {
            return hasBaseType(object.getClass());
        }

        return true;
    }

    private <T> boolean hasBaseType(Class<T> clz) {
        if (clz == Byte.class || clz == Character.class || clz == Boolean.class
                || clz == Short.class || clz == Integer.class || clz == Long.class
                || clz == Float.class || clz == Double.class || clz == String.class) {
            return true;
        }

        return false;
    }

    /**
     * {a.b.c}  => XXX
     *
     * @param environment
     * @param input
     * @return
     */
    private String getFormatString(Environment environment, String input) {
        if (environment != null && StringUtils.isNotBlank(input)) {
            if (input.contains(PARAM_FORMAT_BEGIN) && input.contains(PARAM_FORMAT_END)) {
                //第一个匹配的
                int begin = input.indexOf(PARAM_FORMAT_BEGIN);
                int end = input.indexOf(PARAM_FORMAT_END, begin);
                String oldVal = input.substring(begin + 2, end);
                String newVal = environment.getProperty(oldVal, "");

                //继续查找后面的
                return getFormatString(environment, input.replace(PARAM_FORMAT_BEGIN + oldVal + PARAM_FORMAT_END, newVal));
            }
        }

        return input;
    }


    class ParameterObj {
        /**
         * 参数名称
         */
        private String name;
        /**
         * 参数值
         */
        private Object value;
        /**
         * 参数注解
         */
        private Annotation[] annotations;

        public ParameterObj() {
        }

        ParameterObj(String name, Object value, Annotation[] annotations) {
            this.name = name;
            this.value = value;
            this.annotations = annotations;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Object getValue() {
            return value;
        }

        public void setValue(Object value) {
            this.value = value;
        }

        public Annotation[] getAnnotations() {
            return annotations;
        }

        public void setAnnotations(Annotation[] annotations) {
            this.annotations = annotations;
        }
    }
}
